using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DanielSig.Sorcery;

namespace DanielSig.UnityExtensions
{
	public static class XGameObject
	{
		public static MeshFilter GetMeshFilter(this GameObject obj)
		{
			return (MeshFilter)obj.GetComponent<MeshFilter>();
		}
		public static Mesh GetMesh(this GameObject obj)
		{
			return obj.GetMeshFilter().mesh;
		}
		public static Mesh GetSharedMesh(this GameObject obj)
		{
			return obj.GetMeshFilter().sharedMesh;
		}
		public static Rigidbody GetBody(this GameObject obj)
		{
			return (obj.collider ? obj.collider.attachedRigidbody : null) ?? obj.rigidbody;
		}
		public static T TryGet<T>( this GameObject obj ) where T : Component
		{
			if(obj) return obj.GetComponent<T>();
			return null;
		}
		
		#region scale
		public static void ScaleMesh(this GameObject obj, float x, float y, float z)
		{
			obj.GetMesh().Scale(new Vector3(x, y, z));
		}
		public static void ScaleMesh(this GameObject obj, float scale)
		{
			obj.GetMesh().Scale(new Vector3(scale, scale, scale));
		}
		public static void ScaleMesh(this GameObject obj, Vector3 scalar)
		{
			obj.GetMesh().Scale(scalar);
		}
		#endregion
		
		#region shear
		public static void TransformMesh(this GameObject obj, Matrix4x4 matrix)
		{
			obj.GetMesh().Transform(matrix);
		}
		
		
		public static void RotateMesh(this GameObject obj, Quaternion rotation)
		{
			obj.GetMesh().Transform(Matrix4x4.TRS(Vector3.zero, rotation, Vector3.one));
		}
		
		
		public static void ShearMesh(this GameObject obj, Vector2 xShear, Vector2 yShear, Vector2 zShear)
		{
			obj.GetMesh().Shear(xShear, yShear, zShear);
		}
		
		
		public static void ShearMeshZAboutY(this GameObject obj, float amount)
		{
			obj.GetMesh().ShearMeshZAboutY(amount);
		}
		
		
		public static void ShearMeshZAboutX(this GameObject obj, float amount)
		{
			obj.GetMesh().ShearMeshZAboutX(amount);
		}
		
		
		public static void ShearMeshYAboutZ(this GameObject obj, float amount)
		{
			obj.GetMesh().ShearMeshYAboutZ(amount);
		}
		
		
		public static void ShearMeshYAboutX(this GameObject obj, float amount)
		{
			obj.GetMesh().ShearMeshYAboutX(amount);
		}
		
		
		public static void ShearMeshXAboutZ(this GameObject obj, float amount)
		{
			obj.GetMesh().ShearMeshXAboutZ(amount);
		}
		
		
		public static void ShearMeshXAboutY(this GameObject obj, float amount)
		{
			obj.GetMesh().ShearMeshXAboutY(amount);
		}
		#endregion
		
		public static void MorphMesh(this GameObject obj, Vector3 dir, Vector3 pos, Quaternion q, Vector3 s, int resolution)
		{
			obj.GetMesh().Morph(dir, pos, q, s, resolution);
		}
		
		#region Request Component
		public static Component RequestComponent(this GameObject obj, Type componentType)
		{
			if(!obj) return null;
			Component requested = obj.GetComponent(componentType);
			if(!requested) requested = obj.AddComponent(componentType);
			return requested;
		}
		public static T RequestComponent<T>(this GameObject obj) where T : Component
		{
			if(!obj) return null;
			T requested = obj.GetComponent<T>();
			if(!requested) requested = obj.AddComponent<T>();
			return requested;
		}
		#endregion
		
		#region Local Requests
		#region Special Requests
		public static T RequestCollection<T>(this GameObject obj, String methodName) where T:class, ICollection, new()
		{
			T collection = new T();
			obj.SendMessage(methodName, collection, SendMessageOptions.RequireReceiver);
			return collection;
		}
		public static void ApplyModification<T>(this GameObject comp, String methodName, ref T original) where T:struct
		{
			Value<T> val = new Value<T>();
			val.value = original;
			comp.SendMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			original = val.value;
		}
		public static T RequestBest<T>(this GameObject obj, String methodName)
		{
			Value<Value<T, float>> val = new Value<Value<T, float>>();
			obj.SendMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		public static T RequestBest<T, ParamType>(this GameObject obj, String methodName, ParamType param)
		{
			Value<Value<T, float>, ParamType> val = new Value<Value<T, float>, ParamType>();
			val.param = param;
			obj.SendMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		#endregion
		
		#region No Parameter
		public static T Request<T>(this GameObject obj, String methodName)
		{
			Value<T> val = new Value<T>();
			obj.SendMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T Request<T>(this GameObject obj, String methodName, T defaultReturnValue)
		{
			Value<T> val = new Value<T>();
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			obj.SendMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		
		#region With Parameter
		public static T Request<T, ParamType>(this GameObject obj, String methodName, ParamType param)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			obj.SendMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T Request<T, ParamType>(this GameObject obj, String methodName, ParamType param, T defaultReturnValue)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			obj.SendMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		#endregion
		
		#region Request Upwards
		#region Special Requests
		public static T BegForCollection<T>(this GameObject obj, String methodName) where T:class, ICollection, new()
		{
			T collection = new T();
			obj.SendMessageUpwards(methodName, collection, SendMessageOptions.RequireReceiver);
			return collection;
		}
		public static void BegForModification<T>(this GameObject comp, String methodName, ref T original) where T:struct
		{
			Value<T> val = new Value<T>();
			val.value = original;
			comp.SendMessageUpwards(methodName, val, SendMessageOptions.DontRequireReceiver);
			original = val.value;
		}
		public static T BegForBest<T>(this GameObject obj, String methodName)
		{
			Value<Value<T, float>> val = new Value<Value<T, float>>();
			obj.SendMessageUpwards(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		public static T BegForBest<T, ParamType>(this GameObject obj, String methodName, ParamType param)
		{
			Value<Value<T, float>, ParamType> val = new Value<Value<T, float>, ParamType>();
			val.param = param;
			obj.SendMessageUpwards(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		#endregion
		
		#region No Parameter
		public static T BegFor<T>(this GameObject obj, String methodName)
		{
			Value<T> val = new Value<T>();
			obj.SendMessageUpwards(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T BegFor<T>(this GameObject obj, String methodName, T defaultReturnValue)
		{
			Value<T> val = new Value<T>();
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			obj.SendMessageUpwards(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		
		#region With Parameter
		public static T BegFor<T, ParamType>(this GameObject obj, String methodName, ParamType param)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			obj.SendMessageUpwards(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T BegFor<T, ParamType>(this GameObject obj, String methodName, ParamType param, T defaultReturnValue)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			obj.SendMessageUpwards(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		#endregion
		
		#region Request Downwards
		#region Special Requests
		public static T DemandCollection<T>(this GameObject obj, String methodName) where T:class, ICollection, new()
		{
			T collection = new T();
			obj.BroadcastMessage(methodName, collection, SendMessageOptions.RequireReceiver);
			return collection;
		}
		public static void DemandModification<T>(this GameObject comp, String methodName, ref T original) where T:struct
		{
			Value<T> val = new Value<T>();
			val.value = original;
			comp.BroadcastMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			original = val.value;
		}
		public static T DemandBest<T>(this GameObject obj, String methodName)
		{
			Value<Value<T, float>> val = new Value<Value<T, float>>();
			obj.BroadcastMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		public static T DemandBest<T, ParamType>(this GameObject obj, String methodName, ParamType param)
		{
			Value<Value<T, float>, ParamType> val = new Value<Value<T, float>, ParamType>();
			val.param = param;
			obj.BroadcastMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		#endregion
		
		#region No Parameter
		public static T Demand<T>(this GameObject obj, String methodName)
		{
			Value<T> val = new Value<T>();
			obj.BroadcastMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T Demand<T>(this GameObject obj, String methodName, T defaultReturnValue)
		{
			Value<T> val = new Value<T>();
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			obj.BroadcastMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		
		#region With Parameter
		public static T Demand<T, ParamType>(this GameObject obj, String methodName, ParamType param)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			obj.BroadcastMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T Demand<T, ParamType>(this GameObject obj, String methodName, ParamType param, T defaultReturnValue)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			obj.BroadcastMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		#endregion
		
		#region Other
		#region Lambda
		#region Local
		public static Component GetComponent(this GameObject obj, Func<Component, bool> condition)
		{
			Component[] components = obj.GetComponents<Component>();
			foreach (Component c in components)
			{
				if (condition(c))
				{
					return c;
				}
			}
			return null;
		}
		public static T GetComponent<T>(this GameObject obj, Func<T, bool> condition) where T:Component
		{
			T[] components = obj.GetComponents<T>();
			foreach (T c in components)
			{
				if (condition(c))
				{
					return c;
				}
			}
			return null;
		}
		public static Component[] GetComponents(this GameObject obj, Func<Component, bool> condition)
		{
			Component[] components = obj.GetComponents<Component>();
			int count = 0;
			int length = components.Length;
			for(int i = 0; i < length; i++)
			{
				Component c = components[i];
				if (condition(c))
				{
					components[count++] = c;
				}
			}
			Array.Resize<Component>(ref components, count);
			return components;
		}
		public static T[] GetComponents<T>(this GameObject obj, Func<T, bool> condition) where T:Component
		{
			T[] components = obj.GetComponents<T>();
			int count = 0;
			int length = components.Length;
			for(int i = 0; i < length; i++)
			{
				T c = components[i];
				if (condition(c))
				{
					components[count++] = c;
				}
			}
			Array.Resize<T>(ref components, count);
			return components;
		}
		#endregion
		#region Children
		public static Component GetComponentInChildren(this GameObject obj, Func<Component, bool> condition)
		{
			Component[] components = obj.GetComponentsInChildren<Component>();
			foreach (Component c in components)
			{
				if (condition(c))
				{
					return c;
				}
			}
			return null;
		}
		public static T GetComponentInChildren<T>(this GameObject obj, Func<T, bool> condition) where T:Component
		{
			T[] components = obj.GetComponentsInChildren<T>();
			foreach (T c in components)
			{
				if (condition(c))
				{
					return c;
				}
			}
			return null;
		}
		public static Component[] GetComponentsInChildren(this GameObject obj, Func<Component, bool> condition)
		{
			Component[] components = obj.GetComponentsInChildren<Component>();
			int count = 0;
			int length = components.Length;
			for(int i = 0; i < length; i++)
			{
				Component c = components[i];
				if (condition(c))
				{
					components[count++] = c;
				}
			}
			Array.Resize<Component>(ref components, count);
			return components;
		}
		public static T[] GetComponentsInChildren<T>(this GameObject obj, Func<T, bool> condition) where T:Component
		{
			T[] components = obj.GetComponentsInChildren<T>();
			int count = 0;
			int length = components.Length;
			for(int i = 0; i < length; i++)
			{
				T c = components[i];
				if (condition(c))
				{
					components[count++] = c;
				}
			}
			Array.Resize<T>(ref components, count);
			return components;
		}
		#endregion
		#endregion
		public static T GetComponentByInterface<T>(this GameObject obj)
		{
			Component[] components = obj.GetComponents<Component>();
			foreach (Component c in components)
			{
				if (c is T)
				{
					return (T)(object)c;
				}
			}
			return default(T);
		}
		public static T[] GetComponentsByInterface<T>(this GameObject obj)
		{
			Component[] components = obj.GetComponents<Component>();
			LinkedList<T> results = new LinkedList<T>();
			
			foreach (Component c in components)
			{
				if (c is T)
				{
					results.AddLast((T)(object)c);
				}
			}
			return results.ToArray();
		}
		public static T GetComponentInChildrenByInterface<T>(this GameObject obj, bool includeInactive = false)
		{
			Component[] components = obj.GetComponentsInChildren<Component>(includeInactive);
			foreach (Component c in components)
			{
				if (c is T)
				{
					return (T)(object)c;
				}
			}
			return default(T);
		}
		public static T[] GetComponentsInChildrenByInterface<T>(this GameObject obj)
		{
			Component[] components = obj.GetComponentsInChildren<Component>();
			LinkedList<T> results = new LinkedList<T>();
			
			foreach (Component c in components)
			{
				if (c is T)
				{
					results.AddLast((T)(object)c);
				}
			}
			return results.ToArray();
		}
		public static SortedLinkedList<T> GetComponentsByInterfaceSorted<T>(this GameObject obj)
		{
			Component[] components = obj.GetComponents<Component>();
			SortedLinkedList<T> results = new SortedLinkedList<T>();
			
			foreach (Component c in components)
			{
				if (c is T)
				{
					results.Add((T)(object)c);
				}
			}
			return results;
		}
		
		public static void SendMessageDelayed(this GameObject obj, string messageName, float delayInSeconds, object value, SendMessageOptions options = SendMessageOptions.RequireReceiver)
		{
			obj.CallDelayed("SendMessage", delayInSeconds, messageName, value, options);
		}
		public static void SendMessageDelayed(this GameObject obj, string messageName, float delayInSeconds, SendMessageOptions options = SendMessageOptions.RequireReceiver)
		{
			obj.CallDelayed("SendMessage", delayInSeconds, messageName, options);
		}
		#endregion
		
		#region Rendering
		
		#region Default Layer
		#region Basic
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale));
		}
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Vector3 scale)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale.GetScaled(scale)));
		}
		public static void RenderManually(this GameObject obj, Matrix4x4 matrix)
		{
			MeshFilter filter = obj.GetMeshFilter();
			MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
			if(filter && renderer)
			{
				Graphics.DrawMesh(filter.sharedMesh, matrix, renderer.sharedMaterial, obj.layer);
			}
			foreach(Transform t in obj.transform)
			{
				t.gameObject.RenderManually(matrix * Matrix4x4.TRS(t.localPosition, t.localRotation, t.localScale));
			}
		}
		#endregion
		
		#region One Material
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Material material)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale), material);
		}
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Vector3 scale, Material material)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale.GetScaled(scale)), material);
		}
		public static void RenderManually(this GameObject obj, Matrix4x4 matrix, Material material)
		{
			MeshFilter filter = obj.GetMeshFilter();
			MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
			if(filter && renderer)
			{
				Graphics.DrawMesh(filter.sharedMesh, matrix, material, obj.layer);
			}
			foreach(Transform t in obj.transform)
			{
				t.gameObject.RenderManually(matrix * Matrix4x4.TRS(t.localPosition, t.localRotation, t.localScale), material);
			}
		}
		#endregion
		
		#region Select Material
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Func<GameObject, Material> materialSelector)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale), materialSelector);
		}
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Vector3 scale, Func<GameObject, Material> materialSelector)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale.GetScaled(scale)), materialSelector);
		}
		public static void RenderManually(this GameObject obj, Matrix4x4 matrix, Func<GameObject, Material> materialSelector)
		{
			MeshFilter filter = obj.GetMeshFilter();
			MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
			if(filter && renderer)
			{
				Graphics.DrawMesh(filter.sharedMesh, matrix, materialSelector(obj), obj.layer);
			}
			foreach(Transform t in obj.transform)
			{
				t.gameObject.RenderManually(matrix * Matrix4x4.TRS(t.localPosition, t.localRotation, t.localScale), materialSelector);
			}
		}
		#endregion
		#endregion
		
		#region One Layer
		#region Basic
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, int layer)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale), layer);
		}
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Vector3 scale, int layer)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale.GetScaled(scale)), layer);
		}
		public static void RenderManually(this GameObject obj, Matrix4x4 matrix, int layer)
		{
			MeshFilter filter = obj.GetMeshFilter();
			MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
			if(filter && renderer)
			{
				Graphics.DrawMesh(filter.sharedMesh, matrix, renderer.sharedMaterial, layer);
			}
			foreach(Transform t in obj.transform)
			{
				t.gameObject.RenderManually(matrix * Matrix4x4.TRS(t.localPosition, t.localRotation, t.localScale), layer);
			}
		}
		#endregion
		
		#region One Material
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Material material, int layer)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale), material, layer);
		}
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Vector3 scale, Material material, int layer)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale.GetScaled(scale)), material, layer);
		}
		public static void RenderManually(this GameObject obj, Matrix4x4 matrix, Material material, int layer)
		{
			MeshFilter filter = obj.GetMeshFilter();
			MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
			if(filter && renderer)
			{
				Graphics.DrawMesh(filter.sharedMesh, matrix, material, layer);
			}
			foreach(Transform t in obj.transform)
			{
				t.gameObject.RenderManually(matrix * Matrix4x4.TRS(t.localPosition, t.localRotation, t.localScale), material, layer);
			}
		}
		#endregion
		
		#region Select Material
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Func<GameObject, Material> materialSelector, int layer)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale), materialSelector, layer);
		}
		public static void RenderManually(this GameObject obj, Vector3 position, Quaternion rotation, Vector3 scale, Func<GameObject, Material> materialSelector, int layer)
		{
			obj.RenderManually(Matrix4x4.TRS(position, rotation, obj.transform.localScale.GetScaled(scale)), materialSelector, layer);
		}
		public static void RenderManually(this GameObject obj, Matrix4x4 matrix, Func<GameObject, Material> materialSelector, int layer)
		{
			MeshFilter filter = obj.GetMeshFilter();
			MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
			if(filter && renderer)
			{
				Graphics.DrawMesh(filter.sharedMesh, matrix, materialSelector(obj), layer);
			}
			foreach(Transform t in obj.transform)
			{
				t.gameObject.RenderManually(matrix * Matrix4x4.TRS(t.localPosition, t.localRotation, t.localScale), materialSelector, layer);
			}
		}
		#endregion
		#endregion
		
		#endregion
	}
}

